; 常量
core_code_seg_sel equ 0x38  ;内核代码段选择子
core_data_seg_sel equ 0x30  ;内核数据段选择子
sys_routine_seg_sel equ 0x28  ;公共例程段选择子
video_ram_seg_sel equ 0x20  ;视频缓冲区段选择子
core_stack_seg_sel equ 0x18 ;内核栈段选择子
all_mem_seg_seq equ 0x08  ;整个4GB内存段选择子

FLPY_SECTORS_PER_TRACK equ 18  ;每个磁道的扇区数
FDC_DOR                 equ     0x3F2  ; 数字输出寄存器
FDC_MSR                 equ     0x3F4  ; 主状态寄存器
FDC_FIFO                equ     0x3F5  ; 数据寄存器
;-------------------------------------------------------------------------------
         ;以下是系统核心的头部，用于加载核心程序 
         core_length      dd core_end       ;核心程序总长度#00

         sys_routine_seg  dd section.sys_routine.start
                                            ;系统公用例程段位置#04

         core_data_seg    dd section.core_data.start
                                            ;核心数据段位置#08

         core_code_seg    dd section.core_code.start
                                            ;核心代码段位置#0c


         core_entry       dd start          ;核心代码段入口点#10
                          dw core_code_seg_sel
;-------------------
[bits 32]
;-------------------
SECTION sys_routine vstart=0   ; 系统公共例程段
;-------------------------------------------------------------------------------
;汇编语言程序是极难一次成功，而且调试非常困难。这个例程可以提供帮助 
put_hex_dword:                              ;在当前光标处以十六进制形式显示
                                            ;一个双字并推进光标 
                                            ;输入：EDX=要转换并显示的数字
                                            ;输出：无
         pushad
         push ds
      
         mov ax,core_data_seg_sel           ;切换到核心数据段 
         mov ds,ax
      
         mov ebx,bin_hex                    ;指向核心数据段内的转换表
         mov ecx,8
  .xlt:    
         rol edx,4
         mov eax,edx
         and eax,0x0000000f
         xlat
      
         push ecx
         mov cl,al                           
         call put_char
         pop ecx
       
         loop .xlt
      
         pop ds
         popad
         retf

   ;字符串显示例程
put_string:                                 ;显示0终止的字符串并移动光标 
                                            ;输入：DS:EBX=串地址
         push ecx
  .getc:
         mov cl,[ebx]
         or cl,cl
         jz .exit
         call put_char
         inc ebx
         jmp .getc

  .exit:
         pop ecx
         retf                               ;段间返回

;-------------------------------------------------------------------------------
put_char:                                   ;在当前光标处显示一个字符,并推进
                                            ;光标。仅用于段内调用 
                                            ;输入：CL=字符ASCII码 
         pushad

         ;以下取当前光标位置
         mov dx,0x3d4
         mov al,0x0e
         out dx,al
         inc dx                             ;0x3d5
         in al,dx                           ;高字
         mov ah,al

         dec dx                             ;0x3d4
         mov al,0x0f
         out dx,al
         inc dx                             ;0x3d5
         in al,dx                           ;低字
         mov bx,ax                          ;BX=代表光标位置的16位数

         cmp cl,0x0d                        ;回车符？
         jnz .put_0a
         mov ax,bx
         mov bl,80
         div bl
         mul bl
         mov bx,ax
         jmp .set_cursor

  .put_0a:
         cmp cl,0x0a                        ;换行符？
         jnz .put_other
         add bx,80
         jmp .roll_screen

  .put_other:                               ;正常显示字符
         push es
         mov eax,video_ram_seg_sel          ;0xb8000段的选择子
         mov es,eax
         shl bx,1
         mov [es:bx],cl
         pop es

         ;以下将光标位置推进一个字符
         shr bx,1
         inc bx

  .roll_screen:
         cmp bx,2000                        ;光标超出屏幕？滚屏
         jl .set_cursor

         push ds
         push es
         mov eax,video_ram_seg_sel
         mov ds,eax
         mov es,eax
         cld
         mov esi,0xa0                       ;小心！32位模式下movsb/w/d 
         mov edi,0x00                       ;使用的是esi/edi/ecx 
         mov ecx,1920
         rep movsd
         mov bx,3840                        ;清除屏幕最底一行
         mov ecx,80                         ;32位程序应该使用ECX
  .cls:
         mov word[es:bx],0x0720
         add bx,2
         loop .cls

         pop es
         pop ds

         mov bx,1920

  .set_cursor:
         mov dx,0x3d4
         mov al,0x0e
         out dx,al
         inc dx                             ;0x3d5
         mov al,bh
         out dx,al
         dec dx                             ;0x3d4
         mov al,0x0f
         out dx,al
         inc dx                             ;0x3d5
         mov al,bl
         out dx,al

         popad
         ret   
;--------------------------
; 读 1 个扇区的软盘数据到内存
; eax -- 起始扇区
; ebx -- 载入内存的线性基地址
; 返回值 -- ebx + 512，即下一个扇区的加载线性基地址
;--------------------------
read_floppy_disk_0:
    push eax
    push ecx
    push edx
    push ds
    push es
    push ebx

    mov ebx,eax

    mov eax,core_data_seg_sel
    mov ds,eax                         ;切换DS到内核数据段

    ; 再计算柱面号 
    mov ecx,FLPY_SECTORS_PER_TRACK*2
    mov eax,ebx
    xor edx,edx
    div ecx
    mov [@3],al

    ; 再计算磁头号
    mov ecx,FLPY_SECTORS_PER_TRACK*2
    xor edx,edx
    mov eax,ebx
    div ecx
    mov eax,edx
    xor edx,edx
    mov ecx,FLPY_SECTORS_PER_TRACK
    div ecx
    mov [@3+1],al

    ; 计算扇区号
    mov ecx,18
    mov eax,ebx
    xor edx,edx
    div ecx
    inc edx
    mov [@3+2],dl

    pop ebx

    ; 设置 DMA
    call setup_dma
    ; 重置软盘控制器
    call reset_floppy
    ; 读取数据
    call read_sector

    add ebx,512

    pop es
    pop ds
    pop edx
    pop ecx
    pop eax
    retf

setup_dma:
    ; 1. 禁用 DMA 通道 2
    mov dx, 0x0A       ; DMA 主屏蔽寄存器端口
    mov al, 0x04       ; 禁用通道 2
    out dx, al

    ; 2. 设置 DMA 地址寄存器
    mov eax, ebx
    mov dx, 0x04       ; 通道 2 的地址寄存器
    out dx, al         ; 输出低 8 位
    mov al, ah         ; 高 8 位
    out dx, al

    ; 3. 设置 DMA 页寄存器
    mov dx, 0x81       ; 通道 2 的页寄存器
    shr eax, 16        ; 数据缓冲区段地址（高 8 位段地址）      
    out dx, al

    ; 4. 设置 DMA 传输长度（计数寄存器）
    mov dx, 0x05       ; 通道 2 的计数寄存器
    mov ax, 511        ; 传输字节数减 1（512 - 1 = 511）
    out dx, al         ; 输出低 8 位
    mov al, ah         ; 高 8 位
    out dx, al

    ; 5. 设置 DMA 模式寄存器
    mov dx, 0x0B       ; DMA 模式寄存器
    mov al, 0x56       ; 通道 2，读模式，单字节传输
    out dx, al

    ; 6. 启用 DMA 通道 2
    mov dx, 0x0A       ; DMA 主屏蔽寄存器
    mov al, 0x02       ; 启用通道 2
    out dx, al

; 该设置floppy了
reset_floppy:
    ; 发送复位命令
    mov dx, FDC_DOR
    mov al, 0x00       ; 复位软盘控制器
    out dx, al
    mov al, 0x1C       ; 重新启用软盘控制器和驱动器 0
    out dx, al

        ; 等待复位完成
    call wait_floppy_ready
    ret

    ; 等待软盘控制器就绪
    wait_floppy_ready:
        mov dx, FDC_MSR
    .wait:
        in al, dx
        test al, 0x80      ; 检查主状态寄存器的忙位（第 7 位）
        jz .wait          ; 如果忙，继续等待
        ret

; ============================
; 读取软盘扇区
; ============================
read_sector:
    ; 发送 READ DATA 命令到软盘控制器
    mov dx, FDC_FIFO
    mov al, 0xE6       ; 命令：读数据
    out dx, al
    mov al, [@3+1]       ; 起始头号
    shl al, 2
    or  al, 0x00
    out dx, al
    mov al, [@3]       ; 起始磁道号
    out dx, al
    mov al, [@3+1]       ; 起始头号
    out dx, al
    mov al, [@3+2]       ; 起始扇区号（第2个扇区）
    out dx, al
    mov al, 0x02       ; 每扇区 512 字节
    out dx, al
    mov al, [@3+2]
    inc al
    out dx, al
    mov al, 0x1B       ; GAP3 长度
    out dx, al
    mov al, 0xFF       ; 数据长度（无关）
    out dx, al

    ; 等待操作完成
    call wait_floppy_ready

    ret

;----------------------
; 分配内存
; 输入：ECX=希望分配的字节数
; 输出：ECX=起始线性地址
;----------------------
allocate_memory:                            
push ds
push eax
push ebx

mov eax,core_data_seg_sel
mov ds,eax

mov eax,[ram_alloc]
add eax,ecx                        ;下一次分配时的起始地址

;这里应当有检测可用内存数量的指令

mov ecx,[ram_alloc]                ;返回分配的起始地址

mov ebx,eax
and ebx,0xfffffffc                 ;强制清掉最后2位
add ebx,4                          ;强制对齐 
test eax,0x00000003                ;下次分配的起始地址最好是4字节对齐
cmovnz eax,ebx                     ;如果没有对齐，则强制对齐 
mov [ram_alloc],eax                ;下次从该地址分配内存
                                   ;cmovcc指令可以避免控制转移 
pop ebx
pop eax
pop ds
retf

;====================
; 生成 GDT 描述符
; EAX —— 线性基地址
; EBX —— 段界限
; ECX —— 属性（均在原始位置）
; 返回 EDX：EAX 作为描述符
;====================
make_seg_descriptor:
mov edx,eax ; 因为基地址在描述符的高32位和低32位都有一半数据，因此复制一份，并以此为基础生成描述符
shl eax,16 ; 左移16位
or ax,bx  ; 低16位置为段界限15~0

and edx,0xffff0000 ; 只保留高16位
rol edx,8   ; 循环左移8位
bswap edx   ; 字节互换，最高8位和最低8位互换，中间高8位和低8位互换

xor bx,bx   ; 清除ebx的低16位
or edx,ebx  ; 将ebx的高4位插入edx
or edx,ecx  ; 将属性值插入edx
retf

;-------------------
;在GDT内安装一个新的描述符
;输入：EDX:EAX=描述符 
;输出：CX=描述符的选择子
;-------------------
set_up_gdt_descriptor:
push eax
push ebx
push edx

push ds
push es

mov ebx,core_data_seg_sel          ;切换到核心数据段
mov ds,ebx

mov ebx,all_mem_seg_seq            ;切换到0~4GB的段
mov es,ebx

sgdt [pgdt]                        ;以便开始处理GDT

movzx ebx,word [pgdt]              ;GDT界限(界限是20位的)
inc bx   ; gdt 第一次安装时，gdtr基地址是0x00000000，界限值为0xFFFF。所以必须是bx+1，不能是ebx+1
add ebx,[pgdt+2]                   ;下一个描述符的线性地址

mov [es:ebx],eax
mov [es:ebx+4],edx

add word [pgdt],8                  ;增加一个描述符的大小   

lgdt [pgdt]                        ;对GDT的更改生效 

mov ax,[pgdt]                      ;得到GDT界限值
xor dx,dx
mov bx,8
div bx                             ;除以8，去掉余数
mov cx,ax                          
shl cx,3                           ;将索引号移到正确位置 

pop es
pop ds

pop edx
pop ebx
pop eax

retf
;-------------------
; 将线性地址转换为物理地址
; 输入：EBX = 线性偏移地址
;      EDX = 1--LDT, 0--GDT
; 输入：DS = 段选择子
; 输出：EBX = 物理地址
;-------------------
linear_to_physical:
push eax
push ecx
push edx
push esi
push es

mov ecx,ds
and ecx,0xffff_fff8
push ds

mov eax,core_data_seg_sel
mov ds,eax                         ;切换DS到内核数据段
mov eax,all_mem_seg_seq            ;切换到0~4GB的段
mov es,eax

or edx,edx
jz .gdt
.ldt:
sgdt [pgdt]                        ;以便开始处理GDT
mov eax,[pgdt+0x02]
xor esi,esi
sldt si
and si,0xfff8
mov edx,[es:eax+esi+0x04]      ;得到段的线性基地址(高32位)
bswap edx
rol edx,24
and edx,0xffff_0000

mov eax,[es:eax+esi]      ;得到段的线性基地址(低32位)          
shr eax,16
or edx,eax  ;此时edx即为段的线性基地址
                       
mov eax,edx       ;得到物理地址

jmp .continue
.gdt:
sgdt [pgdt]                        ;以便开始处理GDT
mov eax,[pgdt+0x02]
.continue:
mov edx,[es:eax+ecx+0x04]      ;得到段的线性基地址(高32位)
bswap edx
rol edx,24
and edx,0xffff_0000

mov eax,[es:eax+ecx]      ;得到段的线性基地址(低32位)          
shr eax,16
or edx,eax  ;此时edx即为段的线性基地址

add ebx,edx                        ;得到物理地址

pop ds
pop es
pop esi
pop edx
pop ecx
pop eax

retf

;-------------------------------------------------------------------------------
make_gate_descriptor:                       ;构造门的描述符（调用门等）
                                            ;输入：EAX=门代码在段内偏移地址
                                            ;       BX=门代码所在段的选择子 
                                            ;       CX=段类型及属性等（各属
                                            ;          性位都在原始位置）
                                            ;返回：EDX:EAX=完整的描述符
         push ebx
         push ecx
      
         mov edx,eax
         and edx,0xffff0000                 ;得到偏移地址高16位 
         or dx,cx                           ;组装属性部分到EDX
       
         and eax,0x0000ffff                 ;得到偏移地址低16位 
         shl ebx,16                          
         or eax,ebx                         ;组装段选择子部分
      
         pop ecx
         pop ebx
      
         retf  

;-------------------------------------------------------------------------------
terminate_current_task:                     ;终止当前任务
                                            ;注意，执行此例程时，当前任务仍在
                                            ;运行中。此例程其实也是当前任务的
                                            ;一部分 
         pushfd
         mov edx,[esp]                      ;获得EFLAGS寄存器内容
         add esp,4                          ;恢复堆栈指针

         mov eax,core_data_seg_sel
         mov ds,eax

         test dx,0100_0000_0000_0000B       ;测试NT位
         jnz .b1                            ;当前任务是嵌套的，到.b1执行iretd 
         mov ebx,core_msg1                  ;当前任务不是嵌套的，直接切换到 
         call sys_routine_seg_sel:put_string
         jmp far [prgman_tss]               ;程序管理器任务 
       
  .b1: 
         mov ebx,core_msg0
         call sys_routine_seg_sel:put_string
         iretd             ;返回到嵌套任务，在16位模式下，iretd会加指令前缀0x66，32位模式下和iret一样

sys_routine_end:
;-------------------
SECTION core_data vstart=0   ; 核心数据段
       pgdt             dw  0             ;用于设置和修改GDT 
                        dd  0

       ram_alloc        dd  0x00100000    ;下次分配内存时的起始地址

       ;符号地址检索表
       salt:
       salt_1           db  '@PrintString'
                  times 256-($-salt_1) db 0
                        dd  put_string
                        dw  sys_routine_seg_sel

       salt_2           db  '@ReadDiskData'
              times 256-($-salt_2) db 0
                     dd  read_floppy_disk_0
                     dw  sys_routine_seg_sel

       salt_3           db  '@PrintDwordAsHexString'
              times 256-($-salt_3) db 0
                     dd  put_hex_dword
                     dw  sys_routine_seg_sel

       salt_4           db  '@TerminateProgram'
              times 256-($-salt_4) db 0
                     dd  terminate_current_task
                     dw  sys_routine_seg_sel

       salt_5           db  '@ConvertLinearToPhysical'
              times 256-($-salt_5) db 0
                     dd  linear_to_physical
                     dw  sys_routine_seg_sel

       salt_item_len   equ $-salt_5
       salt_items      equ ($-salt)/salt_item_len

       message_1        db  '  If you seen this message,that means we '
                     db  'are now in protect mode,and the system '
                     db  'core is loaded,and the video display '
                     db  'routine works perfectly.',0x0d,0x0a,0

       message_2        db  '  System wide CALL-GATE mounted.',0x0d,0x0a,0
       
       message_3        db  0x0d,0x0a,'  Loading user program...',0

       message_5        db  '  Loading user program...',0

       do_status        db  'Done.',0x0d,0x0a,0

       message_6        db  0x0d,0x0a,0x0d,0x0a,0x0d,0x0a
                     db  '  User program terminated,control returned.',0

       bin_hex          db '0123456789ABCDEF'
                                          ;put_hex_dword子过程用的查找表 
       core_buf   times 2048 db 0         ;内核用的缓冲区

       esp_pointer      dd 0              ;内核用来临时保存自己的栈指针     

       cpu_brnd0        db 0x0d,0x0a,'  ',0
       cpu_brand  times 52 db 0
       cpu_brnd1        db 0x0d,0x0a,0x0d,0x0a,0

       @3: db 0,0,0    ; 用于存储 柱面、磁头、扇区   

       tcb_chain        dd  0  ; 任务控制块链

       ;程序管理器的任务信息（6字节）
       prgman_tss       dd  0             ;程序管理器的TSS基地址
                        dw  0             ;程序管理器的TSS描述符选择子 
         prgman_msg1      db  0x0d,0x0a
                          db  '[PROGRAM MANAGER]: Hello! I am Program Manager,'
                          db  'run at CPL=0.Now,create user task and switch '
                          db  'to it by the CALL instruction...',0x0d,0x0a,0
                 
         prgman_msg2      db  0x0d,0x0a
                          db  '[PROGRAM MANAGER]: I am glad to regain control.'
                          db  'Now,create another user task and switch to '
                          db  'it by the JMP instruction...',0x0d,0x0a,0
                 
         prgman_msg3      db  0x0d,0x0a
                          db  '[PROGRAM MANAGER]: I am gain control again,'
                          db  'HALT...',0

         core_msg0        db  0x0d,0x0a
                          db  '[SYSTEM CORE]: Uh...This task initiated with '
                          db  'CALL instruction or an exeception/ interrupt,'
                          db  'should use IRETD instruction to switch back...'
                          db  0x0d,0x0a,0

         core_msg1        db  0x0d,0x0a
                          db  '[SYSTEM CORE]: Uh...This task initiated with '
                          db  'JMP instruction,  should switch to Program '
                          db  'Manager directly by the JMP instruction...'
                          db  0x0d,0x0a,0
;-------------------
SECTION core_code vstart=0   ; 核心代码段
;-------------------
fill_descriptor_in_ldt:                     ;在LDT内安装一个新的描述符
                                            ;输入：EDX:EAX=描述符
                                            ;          EBX=TCB基地址
                                            ;输出：CX=描述符的选择子
         push eax
         push edx
         push edi
         push ds

         mov ecx,all_mem_seg_seq
         mov ds,ecx

         mov edi,[ebx+0x0c]                 ;获得LDT基地址
         
         xor ecx,ecx
         mov cx,[ebx+0x0a]                  ;获得LDT界限
         inc cx                             ;LDT的总字节数，即新描述符偏移地址
         
         mov [edi+ecx+0x00],eax
         mov [edi+ecx+0x04],edx             ;安装描述符

         add cx,8                           
         dec cx                             ;得到新的LDT界限值 

         mov [ebx+0x0a],cx                  ;更新LDT界限值到TCB

         mov ax,cx
         xor dx,dx
         mov cx,8
         div cx
         
         mov cx,ax
         shl cx,3                           ;左移3位，并且
         or cx,0000_0000_0000_0100B         ;使TI位=1，指向LDT，最后使RPL=00 

         pop ds
         pop edi
         pop edx
         pop eax
     
         ret
;-------------------
; 加载和重定向用户程序（通过堆栈传递参数）
; 输入：PUSH 逻辑扇区号
;      PUSH 任务控制块基地址
; 返回：无
;-------------------
load_relocate_program: 
pushad

push ds
push es

mov ebp,esp                        ; 为访问通过堆栈传递的参数做准备，使用ebp来访问堆栈

mov ecx,all_mem_seg_seq
mov es,ecx

mov esi,[ebp+11*4]                 ; 从堆栈中取得 TCB 的基地址

; 以下申请创建 LDT 所需要的内存
mov ecx,160                        ; 允许安装 20 个 LDT 描述符，每个描述符 8 字节（64 bit）
call sys_routine_seg_sel:allocate_memory ; 返回的 ecx 是申请到的内存首地址
mov [es:esi+0x0c],ecx              ;登记 LDT 基地址到 TCB 中
mov word [es:esi+0x0a],0xffff      ;登记 LDT 初始的界限到 TCB 中 

; 开始加载用户程序
mov eax,core_data_seg_sel
mov ds,eax                         ;切换DS到内核数据段

mov eax,[ebp+12*4]                        ;读取程序头部数据 
mov ebx,core_buf
xor edx,edx
call sys_routine_seg_sel:linear_to_physical                        
call sys_routine_seg_sel:read_floppy_disk_0

;获取程序总长度，并继续读取，直到所有扇区全部load进内存
mov eax,[core_buf]  

mov ebx,eax  ; 总长度对其到 512 字节
and ebx,0xFFFFFE00
add ebx,512
test eax,0x000001ff
cmovnz eax,ebx

mov ecx,eax ;ecx是即将分配的内存大小
call sys_routine_seg_sel:allocate_memory
mov [es:esi+0x06],ecx                        ;ebx -> 申请到的内存首地址，保存到TCB中
mov ebx,ecx
; push ebx                           ;保存程序加载到内存的首地址 
xor edx,edx
mov ecx,512
div ecx
mov ecx,eax                        ;总扇区数 


mov eax,[ebp+12*4]   ; 从起始扇区开始，读入内存。起始扇区号从栈中取得
.read_disk_to_mem:
call sys_routine_seg_sel:read_floppy_disk_0
inc eax
loop .read_disk_to_mem                           ;循环读，直到读完整个用户程序

mov eax,all_mem_seg_seq         ;切换DS到 0~4GB 的段
mov ds,eax

mov edi,[es:esi+0x06]              ;从TCB中取得程序加载到内存的首地址
mov eax,edi                        ;程序头部起始线性地址
mov ebx,[edi+0x04]                 ;段长度
dec ebx                            ;段界限
mov ecx,0x0040f200                 ;字节粒度的数据段描述符
call sys_routine_seg_sel:make_seg_descriptor
;安装头部段描述符到LDT中 
mov ebx,esi                        ;TCB的基地址
call fill_descriptor_in_ldt

or cx,0000_0000_0000_0011B         ; 设置选择子的特权级为 3
mov [es:esi+0x44],cx               ; 登记程序头部段选择子到 TCB 
mov [edi+0x04],cx                  ; 登记程序头部段选择子到头部 

;建立程序代码段描述符
mov eax,edi
add eax,[edi+0x14]                 ;代码起始线性地址
mov ebx,[edi+0x18]                 ;段长度
dec ebx                            ;段界限
mov ecx,0x0040f800                 ;字节粒度的代码段描述符，特权级3
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi                        ;TCB的基地址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B         ;设置选择子的特权级为3
mov [edi+0x14],cx                  ;登记代码段选择子到头部

;建立程序数据段描述符
mov eax,edi
add eax,[edi+0x1c]                 ;数据段起始线性地址
mov ebx,[edi+0x20]                 ;段长度
dec ebx                            ;段界限
mov ecx,0x0040f200                 ;字节粒度的数据段描述符，特权级3
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi                        ;TCB的基地址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B         ;设置选择子的特权级为3
mov [edi+0x1c],cx                  ;登记数据段选择子到头部

;建立程序堆栈段描述符
mov ecx,[edi+0x0c]                 ;4KB的倍率 
mov ebx,0x000fffff
sub ebx,ecx                        ;得到段界限
mov eax,4096                        
mul ecx                        
mov ecx,eax                        ;准备为堆栈分配内存 
call sys_routine_seg_sel:allocate_memory
add eax,ecx                        ;得到堆栈的高端物理地址 
mov ecx,0x00c0f600                 ;4KB粒度的堆栈段描述符，特权级3
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi                        ;TCB的基地址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B         ;设置选择子的特权级为3
mov [edi+0x08],cx                  ;登记堆栈段选择子到头部

       ;重定向salt表（符号地址映射表）
         mov eax,all_mem_seg_seq         ;这里和前一章不同，头部段描述符
         mov es,eax                         ;已安装，但还没有生效，故只能通
                                            ;过4GB段访问用户程序头部          
         mov eax,core_data_seg_sel
         mov ds,eax
      
         cld

         mov ecx,[es:edi+0x24]                  ;用户程序的SALT条目数
         add edi,0x28                       ;用户程序内的SALT位于头部内0x2c处
  .b2: 
         push ecx
         push edi
      
         mov ecx,salt_items
         mov esi,salt
  .b3:
         push edi
         push esi
         push ecx

         mov ecx,64                         ;检索表中，每条目的比较次数 
         repe cmpsd                         ;每次比较4字节 
         jnz .b4
         mov eax,[esi]                      ;若匹配，esi恰好指向其后的地址数据
         mov [es:edi-256],eax               ;将字符串改写成偏移地址 
         mov ax,[esi+4]
         or ax,0000000000000011B            ;设置 RPL=3
         mov [es:edi-252],ax                ;以及段选择子 
  .b4:
      
         pop ecx
         pop esi
         add esi,salt_item_len
         pop edi                            ;从头比较 
         loop .b3
      
         pop edi
         add edi,256
         pop ecx
         loop .b2

         mov esi,[ebp+11*4]                 ;从堆栈中取得TCB的基地址

         ;创建0特权级堆栈
         mov ecx,4096
         mov eax,ecx                        ;为生成堆栈高端地址做准备 
         mov [es:esi+0x1a],ecx
         shr dword [es:esi+0x1a],12         ;登记0特权级堆栈尺寸到TCB 
         call sys_routine_seg_sel:allocate_memory
         add eax,ecx                        ;堆栈必须使用高端地址为基地址
         mov [es:esi+0x1e],eax              ;登记0特权级堆栈基地址到TCB 
         mov ebx,0xffffe                    ;段长度（界限）
         mov ecx,0x00c09600                 ;4KB粒度，读写，特权级0
         call sys_routine_seg_sel:make_seg_descriptor
         mov ebx,esi                        ;TCB的基地址
         call fill_descriptor_in_ldt
         ;or cx,0000_0000_0000_0000          ;设置选择子的特权级为0
         mov [es:esi+0x22],cx               ;登记0特权级堆栈选择子到TCB
         mov dword [es:esi+0x24],0          ;登记0特权级堆栈初始ESP到TCB
      
         ;创建1特权级堆栈
         mov ecx,4096
         mov eax,ecx                        ;为生成堆栈高端地址做准备
         mov [es:esi+0x28],ecx
         shr dword [es:esi+0x28],12               ;登记1特权级堆栈尺寸到TCB
         call sys_routine_seg_sel:allocate_memory
         add eax,ecx                        ;堆栈必须使用高端地址为基地址
         mov [es:esi+0x2c],eax              ;登记1特权级堆栈基地址到TCB
         mov ebx,0xffffe                    ;段长度（界限）
         mov ecx,0x00c0b600                 ;4KB粒度，读写，特权级1
         call sys_routine_seg_sel:make_seg_descriptor
         mov ebx,esi                        ;TCB的基地址
         call fill_descriptor_in_ldt
         or cx,0000_0000_0000_0001          ;设置选择子的特权级为1
         mov [es:esi+0x30],cx               ;登记1特权级堆栈选择子到TCB
         mov dword [es:esi+0x32],0          ;登记1特权级堆栈初始ESP到TCB

         ;创建2特权级堆栈
         mov ecx,4096
         mov eax,ecx                        ;为生成堆栈高端地址做准备
         mov [es:esi+0x36],ecx
         shr dword [es:esi+0x36],12               ;登记2特权级堆栈尺寸到TCB
         call sys_routine_seg_sel:allocate_memory
         add eax,ecx                        ;堆栈必须使用高端地址为基地址
         mov [es:esi+0x3a],ecx              ;登记2特权级堆栈基地址到TCB
         mov ebx,0xffffe                    ;段长度（界限）
         mov ecx,0x00c0d600                 ;4KB粒度，读写，特权级2
         call sys_routine_seg_sel:make_seg_descriptor
         mov ebx,esi                        ;TCB的基地址
         call fill_descriptor_in_ldt
         or cx,0000_0000_0000_0010          ;设置选择子的特权级为2
         mov [es:esi+0x3e],cx               ;登记2特权级堆栈选择子到TCB
         mov dword [es:esi+0x40],0          ;登记2特权级堆栈初始ESP到TCB
      
         ;在GDT中登记LDT描述符
         mov eax,[es:esi+0x0c]              ;LDT的起始线性地址
         movzx ebx,word [es:esi+0x0a]       ;LDT段界限
         mov ecx,0x00408200                 ;LDT描述符，特权级0
         call sys_routine_seg_sel:make_seg_descriptor
         call sys_routine_seg_sel:set_up_gdt_descriptor
         mov [es:esi+0x10],cx               ;登记LDT选择子到TCB中
       
         ;创建用户程序的TSS
         mov ecx,104                        ;tss的基本尺寸
         mov [es:esi+0x12],cx              
         dec word [es:esi+0x12]             ;登记TSS界限值到TCB 
         call sys_routine_seg_sel:allocate_memory
         mov [es:esi+0x14],ecx              ;登记TSS基地址到TCB
      
         ;登记基本的TSS表格内容
         mov word [es:ecx+0],0              ;反向链=0
      
         mov edx,[es:esi+0x24]              ;登记0特权级堆栈初始ESP
         mov [es:ecx+4],edx                 ;到TSS中
      
         mov dx,[es:esi+0x22]               ;登记0特权级堆栈段选择子
         mov [es:ecx+8],dx                  ;到TSS中
      
         mov edx,[es:esi+0x32]              ;登记1特权级堆栈初始ESP
         mov [es:ecx+12],edx                ;到TSS中

         mov dx,[es:esi+0x30]               ;登记1特权级堆栈段选择子
         mov [es:ecx+16],dx                 ;到TSS中

         mov edx,[es:esi+0x40]              ;登记2特权级堆栈初始ESP
         mov [es:ecx+20],edx                ;到TSS中

         mov dx,[es:esi+0x3e]               ;登记2特权级堆栈段选择子
         mov [es:ecx+24],dx                 ;到TSS中

         mov dx,[es:esi+0x10]               ;登记任务的LDT选择子
         mov [es:ecx+96],dx                 ;到TSS中
      
         mov dx,[es:esi+0x12]               ;登记任务的I/O位图偏移
         mov [es:ecx+102],dx                ;到TSS中 
      
         mov word [es:ecx+100],0            ;T=0

         mov dword [es:ecx+28],0            ;登记CR3(PDBR)

          ;访问用户程序头部，获取数据填充TSS 
         mov ebx,[ebp+11*4]                 ;从堆栈中取得TCB的基地址
         mov edi,[es:ebx+0x06]              ;用户程序加载的基地址 

         mov edx,[es:edi+0x10]              ;登记程序入口点（EIP） 
         mov [es:ecx+32],edx                ;到TSS

         mov dx,[es:edi+0x14]               ;登记程序代码段（CS）选择子
         mov [es:ecx+76],dx                 ;到TSS中

         mov dx,[es:edi+0x08]               ;登记程序堆栈段（SS）选择子
         mov [es:ecx+80],dx                 ;到TSS中

         mov dx,[es:edi+0x04]               ;登记程序数据段（DS）选择子
         mov word [es:ecx+84],dx            ;到TSS中。注意，它指向程序头部段
      
         mov word [es:ecx+72],0             ;TSS中的ES=0

         mov word [es:ecx+88],0             ;TSS中的FS=0

         mov word [es:ecx+92],0             ;TSS中的GS=0

         pushfd
         pop edx
         
         mov dword [es:ecx+36],edx          ;EFLAGS
       
         ;在GDT中登记TSS描述符
         mov eax,[es:esi+0x14]              ;TSS的起始线性地址
         movzx ebx,word [es:esi+0x12]       ;段长度（界限）
         mov ecx,0x00408900                 ;TSS描述符，特权级0
         call sys_routine_seg_sel:make_seg_descriptor
         call sys_routine_seg_sel:set_up_gdt_descriptor
         mov [es:esi+0x18],cx               ;登记TSS选择子到TCB

         mov ax,[es:0x04]


pop es
pop ds

popad

ret 8

;-------------------------------------------------------------------------------
append_to_tcb_link:                         ;在TCB链上追加任务控制块
                                            ;输入：ECX=TCB线性基地址
         push eax
         push edx
         push ds
         push es
         
         mov eax,core_data_seg_sel          ;令DS指向内核数据段 
         mov ds,eax
         mov eax,all_mem_seg_seq         ;令ES指向0..4GB段
         mov es,eax
         
         mov dword [es: ecx+0x00],0         ;当前TCB指针域清零，以指示这是最
                                            ;后一个TCB
                                             
         mov eax,[tcb_chain]                ;TCB表头指针
         or eax,eax                         ;链表为空？
         jz .notcb 
         
  .searc:
         mov edx,eax
         mov eax,[es: edx+0x00]
         or eax,eax               
         jnz .searc
         
         mov [es: edx+0x00],ecx
         jmp .retpc
         
  .notcb:       
         mov [tcb_chain],ecx                ;若为空表，直接令表头指针指向TCB
         
  .retpc:
         pop es
         pop ds
         pop edx
         pop eax
         
         ret
;===============================================================================
start:
mov eax, core_data_seg_sel  ;使 ds 指向内核数据段
mov ds,eax

mov eax, all_mem_seg_seq    ;使 es 指向 0~4GB 段
mov es,eax

mov ebx, message_1
call sys_routine_seg_sel:put_string

;显示处理器品牌信息 
mov eax,0x80000002
cpuid
mov [cpu_brand+0x00],eax
mov [cpu_brand+0x04],ebx
mov [cpu_brand+0x08],ecx
mov [cpu_brand+0x0c],edx

mov eax,0x80000003
cpuid
mov [cpu_brand + 0x10],eax
mov [cpu_brand + 0x14],ebx
mov [cpu_brand + 0x18],ecx
mov [cpu_brand + 0x1c],edx

mov eax,0x80000004
cpuid
mov [cpu_brand + 0x20],eax
mov [cpu_brand + 0x24],ebx
mov [cpu_brand + 0x28],ecx
mov [cpu_brand + 0x2c],edx

mov ebx,cpu_brnd0
call sys_routine_seg_sel:put_string
mov ebx,cpu_brand
call sys_routine_seg_sel:put_string
mov ebx,cpu_brnd1
call sys_routine_seg_sel:put_string

;以下开始安装为整个系统服务的调用门。特权级之间的控制转移必须使用门
mov edi,salt                       ;C-SALT表的起始位置 
mov ecx,salt_items                 ;C-SALT表的条目数量 
  .b3:
         push ecx   
         mov eax,[edi+256]                  ;该条目入口点的32位偏移地址 
         mov bx,[edi+260]                   ;该条目入口点的段选择子 
         mov cx,1_11_0_1100_000_00000B      ;特权级3的调用门(3以上的特权级才
                                            ;允许访问)，0个参数(因为用寄存器
                                            ;传递参数，而没有用栈) 
         call sys_routine_seg_sel:make_gate_descriptor
         call sys_routine_seg_sel:set_up_gdt_descriptor
         mov [edi+260],cx                   ;将返回的门描述符选择子回填
         add edi,salt_item_len              ;指向下一个C-SALT条目 
         pop ecx
         loop .b3

         ;对门进行测试 
         mov ebx,message_2
         call far [salt_1+256]              ;通过门显示信息(偏移量将被忽略) 
      
         mov ebx,message_3                    
         call sys_routine_seg_sel:put_string ;在内核中调用例程不需要通过门

         ; 创建任务管理器的TSS
         mov ecx,104                        ;tss的基本尺寸
         call sys_routine_seg_sel:allocate_memory
         mov [prgman_tss+0x00],ecx          ;保存程序管理器的TSS基地址 

         ;在程序管理器的 TSS 中设置必要的项目 
         mov word [es:ecx+96],0             ;没有LDT。处理器允许没有LDT的任务。
         mov word [es:ecx+102],103          ;没有I/O位图。0特权级事实上不需要。
         mov word [es:ecx+0],0              ;反向链=0
         mov dword [es:ecx+28],0            ;登记CR3(PDBR)
         mov word [es:ecx+100],0            ;T=0
                                            ;除了上述以外，也不需要0、1、2特权级堆栈。0特级不
                                            ;会向低特权级转移控制。
         ; 在 GDT 中安装 TSS 描述符
         mov eax,ecx                        ;TSS的起始线性地址
         mov ebx,103                        ;段长度（界限）
         mov ecx,0x00408900                 ;TSS描述符，特权级0
         call sys_routine_seg_sel:make_seg_descriptor
         call sys_routine_seg_sel:set_up_gdt_descriptor
         mov [prgman_tss+0x04],cx           ;保存程序管理器的TSS描述符选择子 

         ;任务寄存器TR中的内容是任务存在的标志，该内容也决定了当前任务是谁。
         ;下面的指令为当前正在执行的0特权级任务“程序管理器”后补手续（TSS）。
         ltr cx   

         ;现在可认为“程序管理器”任务正执行中
         mov ebx,prgman_msg1
         call sys_routine_seg_sel:put_string

       ; 开始加载用户程序
       ; 创建任务控制块。这不是处理器的要求，而是我们自己为了方便而设立的
         mov ecx,0x46
         call sys_routine_seg_sel:allocate_memory
         call append_to_tcb_link            ;将任务控制块追加到TCB链表 
      
         push dword 49                      ;用户程序位于逻辑50扇区
         push ecx                           ;压入任务控制块起始线性地址 
       
         call load_relocate_program

         call far [es:ecx+0x14]             ;执行任务切换。和上一章不同，任务切
                                            ;换时要恢复TSS内容，所以在创建任务
                                            ;时TSS要填写完整 
      
         ;重新加载并切换任务 
         mov ebx,prgman_msg2
         call sys_routine_seg_sel:put_string

         mov ecx,0x46
         call sys_routine_seg_sel:allocate_memory
         call append_to_tcb_link            ;将此TCB添加到TCB链中

         push dword 49                      ;用户程序位于逻辑50扇区
         push ecx                           ;压入任务控制块起始线性地址

         call load_relocate_program

         jmp far [es:ecx+0x14]              ;执行任务切换

         mov ebx,prgman_msg3
         call sys_routine_seg_sel:put_string

         hlt
         
core_code_end:
;===============================================================================
SECTION core_trail
;-------------------------------------------------------------------------------
core_end: